/*
   Bacula(R) - The Network Backup Solution

   Copyright (C) 2000-2025 Kern Sibbald

   The original author of Bacula is Kern Sibbald, with contributions
   from many others, a complete list can be found in the file AUTHORS.

   You may use this file and others of this release according to the
   license defined in the LICENSE file, which includes the Affero General
   Public License, v3.0 ("AGPLv3") and some additional permissions and
   terms pursuant to its AGPLv3 Section 7.

   This notice must be preserved when any source code is
   conveyed and/or propagated.

   Bacula(R) is a registered trademark of Kern Sibbald.
*/
/*
 *   Parse a Bootstrap Records (used for restores)
 *
 *     Kern Sibbald, June MMII
 *
 */


#include "bacula.h"
#include "stored.h"

static void s_err(const char *file, int line, LEX *lc, const char *msg, ...);
static bool add_restore_volume(JCR *jcr, VOL_LIST *vol, bool add_to_read_list);

typedef BSR * (ITEM_HANDLER)(LEX *lc, BSR *bsr);

static BSR *store_client(LEX *lc, BSR *bsr);
static BSR *store_count(LEX *lc, BSR *bsr);
static BSR *store_device(LEX *lc, BSR *bsr);
static BSR *store_exclude(LEX *lc, BSR *bsr);
static BSR *store_fileregex(LEX *lc, BSR *bsr);
static BSR *store_findex(LEX *lc, BSR *bsr);
static BSR *store_include(LEX *lc, BSR *bsr);
static BSR *store_jobid(LEX *lc, BSR *bsr);
static BSR *store_joblevel(LEX *lc, BSR *bsr);
static BSR *store_job(LEX *lc, BSR *bsr);
static BSR *store_jobtype(LEX *lc, BSR *bsr);
static BSR *store_mediatype(LEX *lc, BSR *bsr);
static BSR *store_nothing(LEX *lc, BSR *bsr);
static BSR *store_sessid(LEX *lc, BSR *bsr);
static BSR *store_sesstime(LEX *lc, BSR *bsr);
static BSR *store_slot(LEX *lc, BSR *bsr);
static BSR *store_stream(LEX *lc, BSR *bsr);
static BSR *store_voladdr(LEX *lc, BSR *bsr);
static BSR *store_volblock(LEX *lc, BSR *bsr);
static BSR *store_volfile(LEX *lc, BSR *bsr);
static BSR *store_vol(LEX *lc, BSR *bsr);
static bool is_fast_rejection_ok(BSR *bsr);
static bool is_positioning_ok(BSR *bsr);

struct kw_items {
   const char *name;
   ITEM_HANDLER *handler;
};

/*
 * List of all keywords permitted in bsr files and their handlers
 */
struct kw_items items[] = {
   {"client",    store_client},
   {"count",     store_count},
   {"device",    store_device},
   {"exclude",   store_exclude},
   {"fileindex", store_findex},
   {"fileregex", store_fileregex},
   {"include",   store_include},
   {"jobid",     store_jobid},
   {"joblevel",  store_joblevel},
   {"job",       store_job},
   {"jobtype",   store_jobtype},
   {"mediatype", store_mediatype},
   {"slot",      store_slot},
   {"storage",   store_nothing},
   {"stream",    store_stream},
   {"voladdr",   store_voladdr},
   {"volblock",  store_volblock},
   {"volume",    store_vol},
   {"volfile",   store_volfile},
   {"volsessionid",   store_sessid},
   {"volsessiontime", store_sesstime},
   {NULL, NULL}
};

/*
 * Create a BSR record
 */
BSR *new_bsr()
{
   BSR *bsr = (BSR *)malloc(sizeof(BSR));
   memset(bsr, 0, sizeof(BSR));
   return bsr;
}


/*********************************************************************
 *
 *      Parse Bootstrap file
 *
 */
BSR *parse_bsr(JCR *jcr, char *fname)
{
   LEX *lc = NULL;
   int token, i;
   BSR *root_bsr = new_bsr();
   BSR *bsr = root_bsr;

   Dmsg1(300, "Enter parse_bsf %s\n", fname);
   if ((lc = lex_open_file(lc, fname, s_err)) == NULL) {
      berrno be;
      Emsg2(M_ERROR_TERM, 0, _("Cannot open bootstrap file %s: %s\n"),
            fname, be.bstrerror());
   }
   lc->caller_ctx = (void *)jcr;
   while ((token=lex_get_token(lc, T_ALL)) != T_EOF) {
      Dmsg1(300, "parse got token=%s\n", lex_tok_to_str(token));
      if (token == T_EOL) {
         continue;
      }
      for (i=0; items[i].name; i++) {
         if (strcasecmp(items[i].name, lc->str) == 0) {
            token = lex_get_token(lc, T_ALL);
            Dmsg1 (300, "in T_IDENT got token=%s\n", lex_tok_to_str(token));
            if (token != T_EQUALS) {
               scan_err1(lc, "expected an equals, got: %s", lc->str);
               bsr = NULL;
               break;
            }
            Dmsg1(300, "calling handler for %s\n", items[i].name);
            /* Call item handler */
            bsr = items[i].handler(lc, bsr);
            i = -1;
            break;
         }
      }
      if (i >= 0) {
         Dmsg1(300, "Keyword = %s\n", lc->str);
         scan_err1(lc, "Keyword %s not found", lc->str);
         bsr = NULL;
         break;
      }
      if (!bsr) {
         break;
      }
   }
   lc = lex_close_file(lc);
   Dmsg0(300, "Leave parse_bsf()\n");
   if (!bsr) {
      free_bsr(root_bsr);
      root_bsr = NULL;
   }
   if (root_bsr) {
      root_bsr->use_fast_rejection = is_fast_rejection_ok(root_bsr);
      root_bsr->use_positioning = is_positioning_ok(root_bsr);
   }
   for (bsr=root_bsr; bsr; bsr=bsr->next) {
      bsr->root = root_bsr;
   }
   return root_bsr;
}

static BSR *store_client(LEX *lc, BSR *bsr)
{
   int token;
   BSR_CLIENT *client;

   for (;;) {
      token = lex_get_token(lc, T_NAME);
      if (token == T_ERROR) {
         return NULL;
      }
      client = (BSR_CLIENT *)malloc(sizeof(BSR_CLIENT));
      memset(client, 0, sizeof(BSR_CLIENT));
      bstrncpy(client->ClientName, lc->str, sizeof(client->ClientName));
      /* Add it to the end of the client chain */
      if (!bsr->client) {
         bsr->client = client;
      } else {
         BSR_CLIENT *bc = bsr->client;
         for ( ;bc->next; bc=bc->next)
            { }
         bc->next = client;
      }
      token = lex_get_token(lc, T_ALL);
      if (token != T_COMMA) {
         break;
      }
   }
   return bsr;
}

static BSR *store_count(LEX *lc, BSR *bsr)
{
   int token;

   token = lex_get_token(lc, T_PINT32);
   if (token == T_ERROR) {
      return NULL;
   }
   bsr->count = lc->pint32_val;
   scan_to_eol(lc);
   return bsr;
}

/* Shove the Device name in each Volume in the current bsr */
static BSR *store_device(LEX *lc, BSR *bsr)
{
   int token;

   token = lex_get_token(lc, T_STRING);
   if (token == T_ERROR) {
      return NULL;
   }
   if (!bsr->volume) {
      Emsg1(M_ERROR,0, _("Device \"%s\" in bsr at inappropriate place.\n"),
         lc->str);
      return bsr;
   }
   BSR_VOLUME *bv;
   for (bv=bsr->volume; bv; bv=bv->next) {
      bstrncpy(bv->device, lc->str, sizeof(bv->device));
   }
   return bsr;
}

static BSR *store_findex(LEX *lc, BSR *bsr)
{
   int token;
   BSR_FINDEX *findex;

   for (;;) {
      token = lex_get_token(lc, T_PINT32_RANGE);
      if (token == T_ERROR) {
         return NULL;
      }
      findex = (BSR_FINDEX *)malloc(sizeof(BSR_FINDEX));
      memset(findex, 0, sizeof(BSR_FINDEX));
      findex->findex = lc->pint32_val;
      findex->findex2 = lc->pint32_val2;
      /* Add it to the end of the chain */
      if (!bsr->FileIndex) {
         bsr->FileIndex = findex;
      } else {
         /* Add to end of chain */
         BSR_FINDEX *bs = bsr->FileIndex;
         for ( ;bs->next; bs=bs->next)
            {  }
         bs->next = findex;
      }
      token = lex_get_token(lc, T_ALL);
      if (token != T_COMMA) {
         break;
      }
   }
   return bsr;
}

static BSR *store_fileregex(LEX *lc, BSR *bsr)
{
   int token;
   int rc;

   token = lex_get_token(lc, T_STRING);
   if (token == T_ERROR) {
      return NULL;
   }

   if (bsr->fileregex) free(bsr->fileregex);
   bsr->fileregex = bstrdup(lc->str);

   if (bsr->fileregex_re == NULL)
      bsr->fileregex_re = (regex_t *)bmalloc(sizeof(regex_t));

   rc = regcomp(bsr->fileregex_re, bsr->fileregex, REG_EXTENDED|REG_NOSUB);
   if (rc != 0) {
      char prbuf[500];
      regerror(rc, bsr->fileregex_re, prbuf, sizeof(prbuf));
      Emsg2(M_ERROR, 0, _("REGEX '%s' compile error. ERR=%s\n"),
            bsr->fileregex, prbuf);
      return NULL;
   }
   return bsr;
}

static BSR *store_jobid(LEX *lc, BSR *bsr)
{
   int token;
   BSR_JOBID *jobid;

   for (;;) {
      token = lex_get_token(lc, T_PINT32_RANGE);
      if (token == T_ERROR) {
         return NULL;
      }
      jobid = (BSR_JOBID *)malloc(sizeof(BSR_JOBID));
      memset(jobid, 0, sizeof(BSR_JOBID));
      jobid->JobId = lc->pint32_val;
      jobid->JobId2 = lc->pint32_val2;
      /* Add it to the end of the chain */
      if (!bsr->JobId) {
         bsr->JobId = jobid;
      } else {
         /* Add to end of chain */
         BSR_JOBID *bs = bsr->JobId;
         for ( ;bs->next; bs=bs->next)
            {  }
         bs->next = jobid;
      }
      token = lex_get_token(lc, T_ALL);
      if (token != T_COMMA) {
         break;
      }
   }
   return bsr;
}


static BSR *store_jobtype(LEX *lc, BSR *bsr)
{
   /* *****FIXME****** */
   Pmsg0(-1, _("JobType not yet implemented\n"));
   return bsr;
}


static BSR *store_joblevel(LEX *lc, BSR *bsr)
{
   /* *****FIXME****** */
   Pmsg0(-1, _("JobLevel not yet implemented\n"));
   return bsr;
}

static BSR *store_job(LEX *lc, BSR *bsr)
{
   int token;
   BSR_JOB *job;

   for (;;) {
      token = lex_get_token(lc, T_NAME);
      if (token == T_ERROR) {
         return NULL;
      }
      job = (BSR_JOB *)malloc(sizeof(BSR_JOB));
      memset(job, 0, sizeof(BSR_JOB));
      bstrncpy(job->Job, lc->str, sizeof(job->Job));
      /* Add it to the end of the client chain */
      if (!bsr->job) {
         bsr->job = job;
      } else {
         /* Add to end of chain */
         BSR_JOB *bc = bsr->job;
         for ( ;bc->next; bc=bc->next)
            { }
         bc->next = job;
      }
      token = lex_get_token(lc, T_ALL);
      if (token != T_COMMA) {
         break;
      }
   }
   return bsr;
}

/* Shove the MediaType in each Volume in the current bsr */
static BSR *store_mediatype(LEX *lc, BSR *bsr)
{
   int token;

   token = lex_get_token(lc, T_STRING);
   if (token == T_ERROR) {
      return NULL;
   }
   if (!bsr->volume) {
      Emsg1(M_ERROR,0, _("MediaType %s in bsr at inappropriate place.\n"),
         lc->str);
      return bsr;
   }
   BSR_VOLUME *bv;
   for (bv=bsr->volume; bv; bv=bv->next) {
      bstrncpy(bv->MediaType, lc->str, sizeof(bv->MediaType));
   }
   return bsr;
}

static BSR *store_vol(LEX *lc, BSR *bsr)
{
   int token;
   BSR_VOLUME *volume;
   char *p, *n;

   token = lex_get_token(lc, T_STRING);
   if (token == T_ERROR) {
      return NULL;
   }
   if (bsr->volume) {
      bsr->next = new_bsr();
      bsr->next->prev = bsr;
      bsr = bsr->next;
   }
   /* This may actually be more than one volume separated by a |
    * If so, separate them.
    */
   for (p=lc->str; p && *p; ) {
      n = strchr(p, '|');
      if (n) {
         *n++ = 0;
      }
      volume = (BSR_VOLUME *)malloc(sizeof(BSR_VOLUME));
      memset(volume, 0, sizeof(BSR_VOLUME));
      bstrncpy(volume->VolumeName, p, sizeof(volume->VolumeName));
      /* Add it to the end of the volume chain */
      if (!bsr->volume) {
         bsr->volume = volume;
      } else {
         BSR_VOLUME *bc = bsr->volume;
         for ( ;bc->next; bc=bc->next)
            { }
         bc->next = volume;
      }
      p = n;
   }
   return bsr;
}

static bool is_positioning_ok(BSR *bsr)
{
   /*
    * Every bsr should have a volfile entry and a volblock entry
    * or a VolAddr
    *   if we are going to use positioning
    */
   for ( ; bsr; bsr=bsr->next) {
      if (!((bsr->volfile && bsr->volblock) || bsr->voladdr)) {
         return false;
      }
   }
   return true;
}

static bool is_fast_rejection_ok(BSR *bsr)
{
   /*
    * Although, this can be optimized, for the moment, require
    *  all bsrs to have both sesstime and sessid set before
    *  we do fast rejection.
    */
   for ( ; bsr; bsr=bsr->next) {
      if (!(bsr->sesstime && bsr->sessid)) {
         return false;
      }
   }
   return true;
}


static BSR *store_nothing(LEX *lc, BSR *bsr)
{
   int token;

   token = lex_get_token(lc, T_STRING);
   if (token == T_ERROR) {
      return NULL;
   }
   return bsr;
}



/*
 * Routine to handle Volume start/end file
 */
static BSR *store_volfile(LEX *lc, BSR *bsr)
{
   int token;
   BSR_VOLFILE *volfile;

   for (;;) {
      token = lex_get_token(lc, T_PINT32_RANGE);
      if (token == T_ERROR) {
         return NULL;
      }
      volfile = (BSR_VOLFILE *)malloc(sizeof(BSR_VOLFILE));
      memset(volfile, 0, sizeof(BSR_VOLFILE));
      volfile->sfile = lc->pint32_val;
      volfile->efile = lc->pint32_val2;
      /* Add it to the end of the chain */
      if (!bsr->volfile) {
         bsr->volfile = volfile;
      } else {
         /* Add to end of chain */
         BSR_VOLFILE *bs = bsr->volfile;
         for ( ;bs->next; bs=bs->next)
            {  }
         bs->next = volfile;
      }
      token = lex_get_token(lc, T_ALL);
      if (token != T_COMMA) {
         break;
      }
   }
   return bsr;
}


/*
 * Routine to handle Volume start/end Block
 */
static BSR *store_volblock(LEX *lc, BSR *bsr)
{
   int token;
   BSR_VOLBLOCK *volblock;

   for (;;) {
      token = lex_get_token(lc, T_PINT32_RANGE);
      if (token == T_ERROR) {
         return NULL;
      }
      volblock = (BSR_VOLBLOCK *)malloc(sizeof(BSR_VOLBLOCK));
      memset(volblock, 0, sizeof(BSR_VOLBLOCK));
      volblock->sblock = lc->pint32_val;
      volblock->eblock = lc->pint32_val2;
      /* Add it to the end of the chain */
      if (!bsr->volblock) {
         bsr->volblock = volblock;
      } else {
         /* Add to end of chain */
         BSR_VOLBLOCK *bs = bsr->volblock;
         for ( ;bs->next; bs=bs->next)
            {  }
         bs->next = volblock;
      }
      token = lex_get_token(lc, T_ALL);
      if (token != T_COMMA) {
         break;
      }
   }
   return bsr;
}

/*
 * Routine to handle Volume start/end address
 */
static BSR *store_voladdr(LEX *lc, BSR *bsr)
{
   int token;
   BSR_VOLADDR *voladdr;

   for (;;) {
      token = lex_get_token(lc, T_PINT64_RANGE);
      if (token == T_ERROR) {
         return NULL;
      }
      voladdr = (BSR_VOLADDR *)malloc(sizeof(BSR_VOLADDR));
      memset(voladdr, 0, sizeof(BSR_VOLADDR));
      voladdr->saddr = lc->pint64_val;
      voladdr->eaddr = lc->pint64_val2;
      /* Add it to the end of the chain */
      if (!bsr->voladdr) {
         bsr->voladdr = voladdr;
      } else {
         /* Add to end of chain */
         BSR_VOLADDR *bs = bsr->voladdr;
         for ( ;bs->next; bs=bs->next)
            {  }
         bs->next = voladdr;
      }
      token = lex_get_token(lc, T_ALL);
      if (token != T_COMMA) {
         break;
      }
   }
   return bsr;
}

static BSR *store_sessid(LEX *lc, BSR *bsr)
{
   int token;
   BSR_SESSID *sid;

   for (;;) {
      token = lex_get_token(lc, T_PINT32_RANGE);
      if (token == T_ERROR) {
         return NULL;
      }
      sid = (BSR_SESSID *)malloc(sizeof(BSR_SESSID));
      memset(sid, 0, sizeof(BSR_SESSID));
      sid->sessid = lc->pint32_val;
      sid->sessid2 = lc->pint32_val2;
      /* Add it to the end of the chain */
      if (!bsr->sessid) {
         bsr->sessid = sid;
      } else {
         /* Add to end of chain */
         BSR_SESSID *bs = bsr->sessid;
         for ( ;bs->next; bs=bs->next)
            {  }
         bs->next = sid;
      }
      token = lex_get_token(lc, T_ALL);
      if (token != T_COMMA) {
         break;
      }
   }
   return bsr;
}

static BSR *store_sesstime(LEX *lc, BSR *bsr)
{
   int token;
   BSR_SESSTIME *stime;

   for (;;) {
      token = lex_get_token(lc, T_PINT32);
      if (token == T_ERROR) {
         return NULL;
      }
      stime = (BSR_SESSTIME *)malloc(sizeof(BSR_SESSTIME));
      memset(stime, 0, sizeof(BSR_SESSTIME));
      stime->sesstime = lc->pint32_val;
      /* Add it to the end of the chain */
      if (!bsr->sesstime) {
         bsr->sesstime = stime;
      } else {
         /* Add to end of chain */
         BSR_SESSTIME *bs = bsr->sesstime;
         for ( ;bs->next; bs=bs->next)
            { }
         bs->next = stime;
      }
      token = lex_get_token(lc, T_ALL);
      if (token != T_COMMA) {
         break;
      }
   }
   return bsr;
}


static BSR *store_stream(LEX *lc, BSR *bsr)
{
   int token;
   BSR_STREAM *stream;

   for (;;) {
      token = lex_get_token(lc, T_INT32);
      if (token == T_ERROR) {
         return NULL;
      }
      stream = (BSR_STREAM *)malloc(sizeof(BSR_STREAM));
      memset(stream, 0, sizeof(BSR_STREAM));
      stream->stream = lc->int32_val;
      /* Add it to the end of the chain */
      if (!bsr->stream) {
         bsr->stream = stream;
      } else {
         /* Add to end of chain */
         BSR_STREAM *bs = bsr->stream;
         for ( ;bs->next; bs=bs->next)
            { }
         bs->next = stream;
      }
      token = lex_get_token(lc, T_ALL);
      if (token != T_COMMA) {
         break;
      }
   }
   return bsr;
}

static BSR *store_slot(LEX *lc, BSR *bsr)
{
   int token;

   token = lex_get_token(lc, T_PINT32);
   if (token == T_ERROR) {
      return NULL;
   }
   if (!bsr->volume) {
      Emsg1(M_ERROR,0, _("Slot %d in bsr at inappropriate place.\n"),
         lc->pint32_val);
      return bsr;
   }
   bsr->volume->Slot = lc->pint32_val;
   scan_to_eol(lc);
   return bsr;
}

static BSR *store_include(LEX *lc, BSR *bsr)
{
   scan_to_eol(lc);
   return bsr;
}

static BSR *store_exclude(LEX *lc, BSR *bsr)
{
   scan_to_eol(lc);
   return bsr;
}

void dump_volfile(BSR_VOLFILE *volfile)
{
   if (volfile) {
      Pmsg2(-1, _("VolFile     : %u-%u\n"), volfile->sfile, volfile->efile);
      dump_volfile(volfile->next);
   }
}

void dump_volblock(BSR_VOLBLOCK *volblock)
{
   if (volblock) {
      Pmsg2(-1, _("VolBlock    : %u-%u\n"), volblock->sblock, volblock->eblock);
      dump_volblock(volblock->next);
   }
}

void dump_voladdr(DEVICE *dev, BSR_VOLADDR *voladdr)
{
   if (voladdr) {
      if (dev) {
         char ed1[50], ed2[50];
         Pmsg2(-1, _("VolAddr    : %s-%llu\n"),
            dev->print_addr(ed1, sizeof(ed1), voladdr->saddr),
            dev->print_addr(ed2, sizeof(ed2), voladdr->eaddr));
      } else {
         Pmsg2(-1, _("VolAddr    : %llu-%llu\n"), voladdr->saddr, voladdr->eaddr);
      }
      dump_voladdr(dev, voladdr->next);
   }
}

void dump_findex(BSR_FINDEX *FileIndex)
{
   if (FileIndex) {
      if (FileIndex->findex == FileIndex->findex2) {
         Pmsg1(-1, _("FileIndex   : %u\n"), FileIndex->findex);
      } else {
         Pmsg2(-1, _("FileIndex   : %u-%u\n"), FileIndex->findex, FileIndex->findex2);
      }
      dump_findex(FileIndex->next);
   }
}

void dump_jobid(BSR_JOBID *jobid)
{
   if (jobid) {
      if (jobid->JobId == jobid->JobId2) {
         Pmsg1(-1, _("JobId       : %u\n"), jobid->JobId);
      } else {
         Pmsg2(-1, _("JobId       : %u-%u\n"), jobid->JobId, jobid->JobId2);
      }
      dump_jobid(jobid->next);
   }
}

void dump_sessid(BSR_SESSID *sessid)
{
   if (sessid) {
      if (sessid->sessid == sessid->sessid2) {
         Pmsg1(-1, _("SessId      : %u\n"), sessid->sessid);
      } else {
         Pmsg2(-1, _("SessId      : %u-%u\n"), sessid->sessid, sessid->sessid2);
      }
      dump_sessid(sessid->next);
   }
}

void dump_volume(BSR_VOLUME *volume)
{
   if (volume) {
      Pmsg1(-1, _("VolumeName  : %s\n"), volume->VolumeName);
      Pmsg1(-1, _("  MediaType : %s\n"), volume->MediaType);
      Pmsg1(-1, _("  Device    : %s\n"), volume->device);
      Pmsg1(-1, _("  Slot      : %d\n"), volume->Slot);
      dump_volume(volume->next);
   }
}


void dump_client(BSR_CLIENT *client)
{
   if (client) {
      Pmsg1(-1, _("Client      : %s\n"), client->ClientName);
      dump_client(client->next);
   }
}

void dump_job(BSR_JOB *job)
{
   if (job) {
      Pmsg1(-1, _("Job          : %s\n"), job->Job);
      dump_job(job->next);
   }
}

void dump_sesstime(BSR_SESSTIME *sesstime)
{
   if (sesstime) {
      Pmsg1(-1, _("SessTime    : %u\n"), sesstime->sesstime);
      dump_sesstime(sesstime->next);
   }
}


void dump_bsr(DEVICE *dev, BSR *bsr, bool recurse)
{
   int64_t save_debug = debug_level;
   debug_level = 1;
   if (!bsr) {
      Pmsg0(-1, _("BSR is NULL\n"));
      debug_level = save_debug;
      return;
   }
   Pmsg1(-1,    _("Next        : 0x%x\n"), bsr->next);
   Pmsg1(-1,    _("Root bsr    : 0x%x\n"), bsr->root);
   dump_volume(bsr->volume);
   dump_sessid(bsr->sessid);
   dump_sesstime(bsr->sesstime);
   dump_volfile(bsr->volfile);
   dump_volblock(bsr->volblock);
   dump_voladdr(dev, bsr->voladdr);
   dump_client(bsr->client);
   dump_jobid(bsr->JobId);
   dump_job(bsr->job);
   dump_findex(bsr->FileIndex);
   if (bsr->count) {
      Pmsg1(-1, _("count       : %u\n"), bsr->count);
      Pmsg1(-1, _("found       : %u\n"), bsr->found);
   }

   Pmsg1(-1,    _("done        : %s\n"), bsr->done?_("yes"):_("no"));
   Pmsg1(-1,    _("positioning : %d\n"), bsr->use_positioning);
   Pmsg1(-1,    _("fast_reject : %d\n"), bsr->use_fast_rejection);
   if (recurse && bsr->next) {
      Pmsg0(-1, "\n");
      dump_bsr(dev, bsr->next, true);
   }
   debug_level = save_debug;
}

/*********************************************************************
 *
 *      Free bsr resources
 */

static void free_bsr_item(BSR *bsr)
{
   BSR *next;
   while (bsr) {
      next = bsr->next;
      free(bsr);
      bsr = next;
   }
}

/*
 * Remove a single item from the bsr tree
 */
void remove_bsr(BSR *bsr)
{
   free_bsr_item((BSR *)bsr->volume);
   free_bsr_item((BSR *)bsr->client);
   free_bsr_item((BSR *)bsr->sessid);
   free_bsr_item((BSR *)bsr->sesstime);
   free_bsr_item((BSR *)bsr->volfile);
   free_bsr_item((BSR *)bsr->volblock);
   free_bsr_item((BSR *)bsr->voladdr);
   free_bsr_item((BSR *)bsr->JobId);
   free_bsr_item((BSR *)bsr->job);
   free_bsr_item((BSR *)bsr->FileIndex);
   free_bsr_item((BSR *)bsr->JobType);
   free_bsr_item((BSR *)bsr->JobLevel);
   if (bsr->fileregex) {
      bfree(bsr->fileregex);
   }
   if (bsr->fileregex_re) {
      regfree(bsr->fileregex_re);
      free(bsr->fileregex_re);
   }
   if (bsr->attr) {
      free_attr(bsr->attr);
   }
   if (bsr->next) {
      bsr->next->prev = bsr->prev;
   }
   if (bsr->prev) {
      bsr->prev->next = bsr->next;
   }
   free(bsr);
}

/*
 * Free all bsrs in chain
 */
void free_bsr(BSR *bsr)
{
   BSR *next_bsr;
   while (bsr) {
      next_bsr = bsr->next;
      /* Remove (free) current bsr */
      remove_bsr(bsr);
      /* Now get the next one */
      bsr = next_bsr;
   }
}

/*****************************************************************
 * Routines for handling volumes
 */
static VOL_LIST *new_restore_volume()
{
   VOL_LIST *vol;
   vol = (VOL_LIST *)malloc(sizeof(VOL_LIST));
   memset(vol, 0, sizeof(VOL_LIST));
   return vol;
}

/*
 * Create a list of Volumes (and Slots and Start positions) to be
 *  used in the current restore job.
 */
void create_restore_volume_list(JCR *jcr, bool add_to_read_list)
{
   char *p, *n;
   VOL_LIST *vol;

   /*
    * Build a list of volumes to be processed
    */
   jcr->NumReadVolumes = 0;
   jcr->CurReadVolume = 0;
   if (jcr->bsr) {
      BSR *bsr = jcr->bsr;
      if (!bsr->volume || !bsr->volume->VolumeName[0]) {
         return;
      }
      for ( ; bsr; bsr=bsr->next) {
         BSR_VOLUME *bsrvol;
         BSR_VOLFILE *volfile;
         uint32_t sfile = UINT32_MAX;

         /* Find minimum start file so that we can forward space to it */
         for (volfile = bsr->volfile; volfile; volfile=volfile->next) {
            if (volfile->sfile < sfile) {
               sfile = volfile->sfile;
            }
         }
         /* Now add volumes for this bsr */
         for (bsrvol = bsr->volume; bsrvol; bsrvol=bsrvol->next) {
            vol = new_restore_volume();
            bstrncpy(vol->VolumeName, bsrvol->VolumeName, sizeof(vol->VolumeName));
            bstrncpy(vol->MediaType,  bsrvol->MediaType,  sizeof(vol->MediaType));
            bstrncpy(vol->device, bsrvol->device, sizeof(vol->device));
            vol->Slot = bsrvol->Slot;
            vol->start_file = sfile;
            if (add_restore_volume(jcr, vol, add_to_read_list)) {
               jcr->NumReadVolumes++;
               Dmsg2(400, "Added volume=%s mediatype=%s\n", vol->VolumeName,
                  vol->MediaType);
            } else {
               Dmsg1(400, "Duplicate volume %s\n", vol->VolumeName);
               free((char *)vol);
            }
            sfile = 0;                /* start at beginning of second volume */
         }
      }
   } else {
      /* This is the old way -- deprecated */
      for (p = jcr->dcr->VolumeName; p && *p; ) {
         n = strchr(p, '|');             /* volume name separator */
         if (n) {
            *n++ = 0;                    /* Terminate name */
         }
         vol = new_restore_volume();
         bstrncpy(vol->VolumeName, p, sizeof(vol->VolumeName));
         bstrncpy(vol->MediaType, jcr->dcr->media_type, sizeof(vol->MediaType));
         if (add_restore_volume(jcr, vol, add_to_read_list)) {
            jcr->NumReadVolumes++;
         } else {
            free((char *)vol);
         }
         p = n;
      }
   }
}

/*
 * Add current volume to end of list, only if the Volume
 * is not already in the list.
 *
 *   returns: 1 if volume added
 *            0 if volume already in list
 */
static bool add_restore_volume(JCR *jcr, VOL_LIST *vol, bool add_to_read_list)
{
   VOL_LIST *next = jcr->VolList;

   if (add_to_read_list) {
      /* Add volume to volume manager's read list */
      add_read_volume(jcr, vol->VolumeName);
   }

   if (!next) {                       /* list empty ? */
      jcr->VolList = vol;             /* yes, add volume */
   } else {
      /* Loop through all but last */
      for ( ; next->next; next=next->next) {
         if (strcmp(vol->VolumeName, next->VolumeName) == 0) {
            /* Save smallest start file */
            if (vol->start_file < next->start_file) {
               next->start_file = vol->start_file;
            }
            return false;              /* already in list */
         }
      }
      /* Check last volume in list */
      if (strcmp(vol->VolumeName, next->VolumeName) == 0) {
         if (vol->start_file < next->start_file) {
            next->start_file = vol->start_file;
         }
         return false;                /* already in list */
      }
      next->next = vol;               /* add volume */
   }
   return true;
}

void free_restore_volume_list(JCR *jcr)
{
   VOL_LIST *vol = jcr->VolList;
   VOL_LIST *tmp;

   for ( ; vol; ) {
      tmp = vol->next;
      remove_read_volume(jcr, vol->VolumeName);
      free(vol);
      vol = tmp;
   }
   jcr->VolList = NULL;
}


/*
 * Format a scanner error message
 */
static void s_err(const char *file, int line, LEX *lc, const char *msg, ...)
{
   JCR *jcr = (JCR *)(lc->caller_ctx);
   va_list arg_ptr;
   char buf[MAXSTRING];

   va_start(arg_ptr, msg);
   bvsnprintf(buf, sizeof(buf), msg, arg_ptr);
   va_end(arg_ptr);

   if (jcr) {
      Jmsg(jcr, M_FATAL, 0, _("Bootstrap file error: %s\n"
"            : Line %d, col %d of file %s\n"),
         buf, lc->line_no, lc->col_no, lc->fname);
   } else {
      e_msg(file, line, M_FATAL, 0, _("Bootstrap file error: %s\n"
"            : Line %d, col %d of file %s\n"),
         buf, lc->line_no, lc->col_no, lc->fname);
   }

   lc->last_result = -1;
}
